Skip to content

feat: improve feature flag support#598

Open
vivekkhimani wants to merge 2 commits intoworkos:mainfrom
vivekkhimani:vivek/feature-flags
Open

feat: improve feature flag support#598
vivekkhimani wants to merge 2 commits intoworkos:mainfrom
vivekkhimani:vivek/feature-flags

Conversation

@vivekkhimani
Copy link
Contributor

@vivekkhimani vivekkhimani commented Mar 21, 2026

Summary

Adds a standalone FeatureFlags module to the SDK for managing feature flags via the WorkOS API. Previously, feature flag support was limited to scoped list queries on Organizations and UserManagement, but this change fills in the rest of the API surface.

What changed

New module: workos.feature_flags

Exposes the following operations through client.feature_flags:

  • list_feature_flags() — paginated list of all flags (GET /feature-flags)
  • get_feature_flag(slug) — fetch a single flag by slug (GET /feature-flags/{slug})
  • enable_feature_flag(slug) / disable_feature_flag(slug) — toggle flags (PUT /feature-flags/{slug}/enable|disable)
  • add_feature_flag_target(slug, resource_id) — add a user or org as a target (POST /feature-flags/{slug}/targets/{resourceId})
  • remove_feature_flag_target(slug, resource_id) — remove a target (DELETE /feature-flags/{slug}/targets/{resourceId})

Both sync and async clients are supported.

Updated FeatureFlag model

The existing model was missing a few fields that the API actually returns. Added tags, enabled, and default_value.

Housekeeping

Moved the FeatureFlagsListResource type alias out of organizations.py into the new feature_flags module so there's a single definition. organizations.py and user_management.py now import it from there.

Test plan

  • New test file tests/test_feature_flags.py covering all 6 endpoints (sync + async)
  • Existing org and user management feature flag tests still pass
  • Full suite green (779 tests), mypy clean, ruff clean

Documentation

Does this require changes to the WorkOS Docs? For example, does the API Reference or any code snippets need updates?

  • Yes

If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.

Closes #472

@vivekkhimani vivekkhimani requested review from a team as code owners March 21, 2026 20:14
@vivekkhimani vivekkhimani requested a review from amygdalama March 21, 2026 20:14
@vivekkhimani vivekkhimani changed the title improve feature flag support feat: improve feature flag support Mar 21, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR adds a standalone workos.feature_flags module that exposes the full FeatureFlags API surface (list, get, enable, disable, add_target, remove_target) for both sync and async clients, and fills in three previously missing fields (tags, enabled, default_value) on the FeatureFlag model. It also consolidates the FeatureFlagsListResource type alias — previously duplicated in organizations.py and user_management.py — into a single canonical definition in the new module.

  • src/workos/feature_flags.py: New module implementing all 6 endpoints via FeatureFlags (sync) and AsyncFeatureFlags (async), following the established SDK patterns exactly.
  • src/workos/types/feature_flags/feature_flag.py: Adds tags: Sequence[str], enabled: bool, and default_value: Optional[Any] to the model — the last field uses Any which is permissive; worth narrowing if the API spec defines concrete value types.
  • src/workos/organizations.py / user_management.py: Remove local duplicate FeatureFlagsListResource definitions and import from the new canonical source; existing FeatureFlag and FeatureFlagListFilters imports are still used in the scoped list methods.
  • src/workos/client.py / async_client.py: Expose feature_flags property using the same lazy-initialization pattern as every other module.
  • tests/test_feature_flags.py: Full coverage of all 6 endpoints for both sync and async variants; URL, HTTP method, and response shape are all verified.

Confidence Score: 5/5

  • This PR is safe to merge — it's a clean additive feature with no breaking changes, consistent patterns, and full test coverage.
  • All 6 new endpoints are implemented symmetrically for sync and async clients, following existing SDK conventions exactly. The type consolidation (FeatureFlagsListResource) is straightforward and verified to leave all existing imports intact. The two comments are non-blocking style suggestions (loose Any typing and lack of URL encoding for path params), neither of which affects current functional correctness.
  • No files require special attention. feature_flag.py has the loosely-typed default_value: Optional[Any] worth a second look if the API spec narrows the type.

Important Files Changed

Filename Overview
src/workos/feature_flags.py New module implementing the full FeatureFlags API surface (list, get, enable, disable, add/remove target) for both sync and async clients. Follows existing SDK patterns closely. Minor: resource_id and slug are not URL-encoded before path interpolation.
src/workos/types/feature_flags/feature_flag.py Adds tags, enabled, and default_value fields to the model. default_value: Optional[Any] is loosely typed — a union of concrete types would be safer if the API spec defines the possible values.
src/workos/organizations.py Removes duplicate local FeatureFlagsListResource definition and imports the canonical one from workos.feature_flags. Existing imports of FeatureFlag and FeatureFlagListFilters are still used in the scoped list methods.
src/workos/user_management.py Same housekeeping as organizations.py — removes local FeatureFlagsListResource definition and imports from the new canonical source. Existing imports remain valid.
src/workos/client.py Adds feature_flags property to SyncClient following the existing lazy-initialization pattern used by all other modules.
src/workos/async_client.py Adds feature_flags property to AsyncClient following the same lazy-initialization pattern as the sync client. No issues.
tests/test_feature_flags.py New test file covering all 6 endpoints (sync + async via the sync_and_async mark). Tests verify HTTP method, URL shape, and return value for every operation. Coverage is solid.
tests/utils/fixtures/mock_feature_flag.py Updated to accept an enabled parameter and provide the three new model fields (tags, enabled, default_value). Consistent with the updated FeatureFlag model.

Sequence Diagram

sequenceDiagram
    participant C as Client (sync/async)
    participant FF as FeatureFlags module
    participant H as HTTPClient
    participant API as WorkOS API

    C->>FF: list_feature_flags(limit, before, after, order)
    FF->>H: GET /feature-flags?limit=...
    H->>API: GET /feature-flags
    API-->>H: { data: [...], list_metadata: {...} }
    H-->>FF: response dict
    FF-->>C: FeatureFlagsListResource

    C->>FF: get_feature_flag(slug)
    FF->>H: GET /feature-flags/{slug}
    H->>API: GET /feature-flags/{slug}
    API-->>H: feature flag object
    H-->>FF: response dict
    FF-->>C: FeatureFlag

    C->>FF: enable_feature_flag(slug)
    FF->>H: PUT /feature-flags/{slug}/enable  (body: {})
    H->>API: PUT /feature-flags/{slug}/enable
    API-->>H: updated feature flag
    H-->>FF: response dict
    FF-->>C: FeatureFlag (enabled=true)

    C->>FF: disable_feature_flag(slug)
    FF->>H: PUT /feature-flags/{slug}/disable  (body: {})
    H->>API: PUT /feature-flags/{slug}/disable
    API-->>H: updated feature flag
    H-->>FF: response dict
    FF-->>C: FeatureFlag (enabled=false)

    C->>FF: add_feature_flag_target(slug, resource_id)
    FF->>H: POST /feature-flags/{slug}/targets/{resource_id}  (body: {})
    H->>API: POST /feature-flags/{slug}/targets/{resource_id}
    API-->>H: 200 OK
    H-->>FF: (ignored)
    FF-->>C: None

    C->>FF: remove_feature_flag_target(slug, resource_id)
    FF->>H: DELETE /feature-flags/{slug}/targets/{resource_id}
    H->>API: DELETE /feature-flags/{slug}/targets/{resource_id}
    API-->>H: 200 OK
    H-->>FF: (ignored)
    FF-->>C: None
Loading

Last reviewed commit: "improve feature flag..."

Comment on lines +167 to +172
def add_feature_flag_target(self, slug: str, resource_id: str) -> None:
self._http_client.request(
f"{FEATURE_FLAGS_PATH}/{slug}/targets/{resource_id}",
method=REQUEST_METHOD_POST,
json={},
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 resource_id interpolated directly into URL path

resource_id is supplied by the caller and is embedded verbatim into the URL path. The docstring documents the expected format as user_<id> or org_<id>, but there is no validation or URL-encoding applied. A value containing /, ?, or # would silently alter the request URL.

The same pattern applies to slug throughout the module, and this is consistent with how other modules in the SDK build paths. Since WorkOS-generated IDs won't contain these characters in practice, the risk is low — but a lightweight guard (e.g., urllib.parse.quote) on resource_id (and similarly for slug) would make the surface more robust against malformed input:

from urllib.parse import quote

f"{FEATURE_FLAGS_PATH}/{quote(slug)}/targets/{quote(resource_id)}"

This also applies to the same pattern in the AsyncFeatureFlags counterpart (line 241–244).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is low-risk and consistent with how some other modules handle it but if you'd like me to do this, I am happy to!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Add support for listing feature flags

1 participant